﻿// -----------------------------------------------------------------------
// <copyright file="Serializer.cs" company="none">
// Copyright (c) 2012 M. Alberti, xanatos(at)live.it
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
// </copyright>
// -----------------------------------------------------------------------

namespace FluentSerializer
{
    using System;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.IO;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Reflection;
    using System.Reflection.Emit;
    using CollectionsHelpers;
    using FluentStatement.Utilities;
    using Generators;
    using Serializers;
    using Utilities;

    /// <summary>
    /// 
    /// </summary>
    public abstract partial class Serializer
    {
        /// <summary>
        /// 
        /// </summary>
        public const int Growth = 4096;

        /// <summary>
        /// 
        /// </summary>
        public static readonly ConcurrentDictionary<Type, Descriptor> UniversalSerializers = new ConcurrentDictionary<Type, Descriptor>(10, 200);

        /// <summary>
        /// 
        /// </summary>
        public static readonly Dictionary<Type, Generator> UniversalGenerators = new Dictionary<Type, Generator>
        {
            { typeof(Enum), EnumGenerator.BuildSerializer },
            /***/
            { typeof(Tuple<>), TupleGenerator.BuildSerializer },
            { typeof(Tuple<,>), TupleGenerator.BuildSerializer },
            { typeof(Tuple<,,>), TupleGenerator.BuildSerializer },
            { typeof(Tuple<,,,>), TupleGenerator.BuildSerializer },
            { typeof(Tuple<,,,,>), TupleGenerator.BuildSerializer },
            { typeof(Tuple<,,,,,>), TupleGenerator.BuildSerializer },
            { typeof(Tuple<,,,,,,>), TupleGenerator.BuildSerializer },
            { typeof(Tuple<,,,,,,,>), TupleGenerator.BuildSerializer },
            /***/
            { typeof(KeyValuePair<,>), TupleGenerator.BuildSerializer },
            /***/
            { typeof(ICollectionHelper), ICollectionHelper },
            /***/
            { typeof(Array), ArrayGenerator.BuildSerializer },
            /***/
            { typeof(DirectList<>), DirectListGenerator.BuildSerializer },
            { typeof(List<>), ICollectionGenerator.BuildSerializer },
            { typeof(ICollection<>), ICollectionGenerator.BuildSerializer },
            { typeof(IList<>), ICollectionGenerator.BuildSerializer },
            /***/
            { typeof(HashSet<>), ISetGenerator.BuildSerializer },
            { typeof(SortedSet<>), ISetGenerator.BuildSerializer },
            { typeof(ISet<>), ISetGenerator.BuildSerializer },
            /***/
            { typeof(IDictionary<,>), IDictionaryGenerator.BuildSerializer },
            { typeof(Dictionary<,>), IDictionaryGenerator.BuildSerializer },
            { typeof(SortedDictionary<,>), IDictionaryGenerator.BuildSerializer },
            { typeof(ConcurrentDictionary<,>), IDictionaryGenerator.BuildSerializer },
            /***/
            { typeof(Nullable<>), OptionalGenerator.BuildSerializer },
            { typeof(Optional<>), OptionalGenerator.BuildSerializer },
            /***/
            { typeof(UnixDateTime<>), UnixDateTimeGenerator.BuildSerializer },
        };

        /// <summary>
        /// 
        /// </summary>
        public static readonly ParameterExpression ReaderExpression = Expression.Parameter(typeof(BinaryReaderStream));

        /// <summary>
        /// 
        /// </summary>
        public static readonly ParameterExpression WriterExpression = Expression.Parameter(typeof(BinaryWriterStream));

        /// <summary>
        /// 
        /// </summary>
        public static readonly ConstantExpression Zero = Expression.Constant(0);

        /// <summary>
        /// 
        /// </summary>
        protected static readonly Dictionary<Type, int> BaseTypes = new Dictionary<Type, int>
        { 
            { typeof(sbyte), sizeof(sbyte) }, 
            { typeof(byte), sizeof(byte) }, 
            { typeof(bool), sizeof(bool) },
            { typeof(short), sizeof(short) }, 
            { typeof(ushort), sizeof(ushort) }, 
            { typeof(char), sizeof(char) }, 
            { typeof(int), sizeof(int) }, 
            { typeof(uint), sizeof(uint) }, 
            { typeof(long), sizeof(long) }, 
            { typeof(ulong), sizeof(ulong) },
            { typeof(float), sizeof(float) }, 
            { typeof(double), sizeof(double) }, 
            { typeof(IntPtr), IntPtr.Size }, 
            { typeof(UIntPtr), UIntPtr.Size },
        };

        /// <summary>
        /// 
        /// </summary>
        static Serializer()
        {
            foreach (var baseType in BaseTypes)
            {
                UniversalSerializers[baseType.Key] = BaseTypeSerializers.BuildBaseTypeSerializer(baseType.Key, baseType.Value);

                Type array = baseType.Key.MakeArrayType();
                UniversalSerializers[typeof(FixedLength<>).MakeGenericType(array)] = BaseTypeSerializers.BuildBaseTypeFixedLengthArraySerializer(array, baseType.Value);

                Type directList = typeof(DirectList<>).MakeGenericType(baseType.Key);
                UniversalSerializers[typeof(FixedLength<>).MakeGenericType(directList)] = BaseTypeSerializers.BuildBaseTypeFixedLengthDirectListSerializer(directList, baseType.Value);
            }

            UniversalSerializers[typeof(DateTime)] = DateTimeSerializer.BuildDateTimeSerializer();
            UniversalSerializers[typeof(TimeSpan)] = TimeSpanSerializer.BuildDateTimeSerializer();
            UniversalSerializers[typeof(decimal)] = DecimalSerializer.BuildDecimalSerializer();
        }

        /// <summary>
        /// 
        /// </summary>
        protected Serializer()
        {
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="serializer"></param>
        /// <param name="parametersType"></param>
        /// <param name="type"></param>
        /// <param name="helper"></param>
        /// <returns></returns>
        public delegate Descriptor Generator(Serializer serializer, Type parametersType, Type type, Type helper);

        /// <summary>
        /// 
        /// </summary>
        /// <param name="type"></param>
        public static void PreCompile(Type type)
        {
            GetSerializer(type);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static Type UndecorateType(Type type)
        {
            while (typeof(IModifier).IsAssignableFrom(type))
            {
                type = type.GetGenericArguments()[0];
            }

            if (type.IsGenericType)
            {
                Type[] subTypes = type.GetGenericArguments();
                Type[] undecoratedSubTypes = Array.ConvertAll(subTypes, p => UndecorateType(p));
                type = type.GetGenericTypeDefinition().MakeGenericType(undecoratedSubTypes);
            }

            return type;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="expression"></param>
        /// <param name="type"></param>
        /// <returns></returns>
        [DebuggerHidden]
        public static bool IsConvertible(Expression expression, Type type)
        {
            UnaryExpression convert = Expression.ConvertChecked(expression, type);
            Action check = Expression.Lambda<Action>(convert).Compile();

            try
            {
                check();
                return true;
            }
            catch
            {
                return false;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="body"></param>
        /// <param name="removeLastParameter"></param>
        /// <param name="parameters"></param>
        /// <returns></returns>
        public static LambdaExpression MakeLambda(Expression body, bool removeLastParameter, params ParameterExpression[] parameters)
        {
            return Expression.Lambda(body, removeLastParameter ? parameters.Take(parameters.Length - 1) : parameters);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="body"></param>
        /// <param name="removeLastParameter"></param>
        /// <param name="parameters"></param>
        /// <returns></returns>
        public static Expression MakeFromLambda(LambdaExpression body, bool removeLastParameter, params Expression[] parameters)
        {
            return DirectLambdaExpression.Prepare(body, removeLastParameter ? parameters.Take(parameters.Length - 1) : parameters);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static LambdaExpression GetDisposeExpression(Type type)
        {
            var methods = type.GetVisibleMethods();

            MethodInfo dispose = methods.Where(p => p.Name == "Dispose" && !p.IsStatic && !p.IsGenericMethod && p.ReturnType == typeof(void) && p.GetParameters().Length == 0).FirstOrDefault();

            if (dispose == null)
            {
                dispose = type.GetInterfaceMap(typeof(IDisposable)).TargetMethods[0];
            }

            ParameterExpression item = Expression.Parameter(type);

            LambdaExpression expression = Expression.Lambda(Expression.TryCatch(Expression.Call(item, dispose), Expression.MakeCatchBlock(typeof(object), null, Expression.Empty(), null)), item);
            return expression;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static Descriptor GetSerializer(Type type)
        {
            return GetSerializer(null, type, type, null);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="serializer"></param>
        /// <param name="type"></param>
        /// <param name="requestedType"></param>
        /// <param name="helper"></param>
        /// <returns></returns>
        public static Descriptor GetSerializer(Serializer serializer, Type type, Type requestedType, Type helper)
        {
            if (serializer != null)
            {
                return serializer.GetSerializer(type, requestedType, helper);
            }

            Descriptor descriptor;

            if (typeof(IModifier).IsAssignableFrom(requestedType) && requestedType.IsGenericTypeDefinition)
            {
                requestedType = requestedType.MakeGenericType(type);
            }

            if (UniversalSerializers.TryGetValue(requestedType, out descriptor))
            {
                return descriptor;
            }

            Type realType;

            if (requestedType.IsGenericType)
            {
                if (typeof(ICollectionHelper).IsAssignableFrom(requestedType))
                {
                    realType = typeof(ICollectionHelper);
                }
                else
                {
                    realType = requestedType.GetGenericTypeDefinition();
                }
            }
            else
            {
                if (requestedType.IsEnum)
                {
                    realType = typeof(Enum);
                }
                else if (requestedType.IsArray)
                {
                    realType = typeof(Array);
                }
                else
                {
                    realType = requestedType;
                }
            }

            Generator generator;

            if (UniversalGenerators.TryGetValue(realType, out generator))
            {
                descriptor = generator(null, null, requestedType, helper);

                if (descriptor.IsUniversal)
                {
                    UniversalSerializers[descriptor.Type] = descriptor;
                }
                else
                {
                    throw new NotSupportedException();
                }

                return descriptor;
            }

            throw new NotSupportedException();
        }

        /// <summary>
        /// 
        /// </summary>
        public static void Dump()
        {
            foreach (var descriptor in UniversalSerializers)
            {
                Dump(descriptor, "U");
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="type"></param>
        /// <param name="requestedType"></param>
        /// <param name="helper"></param>
        /// <returns></returns>
        public abstract Descriptor GetSerializer(Type type, Type requestedType, Type helper);

        /// <summary>
        /// 
        /// </summary>
        /// <param name="descriptor"></param>
        /// <param name="type"></param>
        protected static void Dump(KeyValuePair<Type, Descriptor> descriptor, string type)
        {
            var typeName = descriptor.Key.ToString().Replace("+", ".");
            string baseFileName = "FS_" + type + "_" + typeName;
            string fileName = baseFileName + ".dll";
            string errorFileName = baseFileName + ".txt";

            if (File.Exists(fileName))
            {
                File.Delete(fileName);
            }

            if (File.Exists(errorFileName))
            {
                File.Delete(errorFileName);
            }

            typeName = typeName.Replace(",", "_");

            try
            {
                var da = AppDomain.CurrentDomain.DefineDynamicAssembly(
                    new AssemblyName(typeName), // call it whatever you want
                    AssemblyBuilderAccess.Save);

                var dm = da.DefineDynamicModule(typeName, fileName);
                var dt = dm.DefineType(descriptor.Key.Name, TypeAttributes.Public | TypeAttributes.Abstract | TypeAttributes.Sealed);

                if (descriptor.Value.DisposeExpression != null)
                {
                    var method2 = dt.DefineMethod("Dispose", MethodAttributes.Public | MethodAttributes.Static);
                    descriptor.Value.DisposeExpression.CompileToMethod(method2);
                }

                var method = dt.DefineMethod("Deserialize", MethodAttributes.Public | MethodAttributes.Static);
                descriptor.Value.DeserializeExpression.CompileToMethod(method);

                method = dt.DefineMethod("Serialize", MethodAttributes.Public | MethodAttributes.Static);
                descriptor.Value.SerializeExpression.CompileToMethod(method);

                method = dt.DefineMethod("Size", MethodAttributes.Public | MethodAttributes.Static);
                descriptor.Value.SizeExpression.CompileToMethod(method);

                dt.CreateType();
                da.Save(fileName);
            }
            catch (Exception ex)
            {
                File.WriteAllText(errorFileName, ex.ToString());
            }
        }
    }
}